/**
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package org.cspoker.ai.bots.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.EnumSet;
import java.util.HashMap;

import org.cspoker.ai.opponentmodels.weka.PlayerData;
import org.cspoker.ai.opponentmodels.weka.Propositionalizer;
import org.cspoker.ai.opponentmodels.weka.instances.EmptyInstance;
import org.cspoker.ai.opponentmodels.weka.instances.InstancesBuilder;
import org.cspoker.ai.opponentmodels.weka.instances.PostCheckBetInstances;
import org.cspoker.ai.opponentmodels.weka.instances.PostFoldCallRaiseInstances;
import org.cspoker.ai.opponentmodels.weka.instances.PreCheckBetInstances;
import org.cspoker.ai.opponentmodels.weka.instances.PreFoldCallRaiseInstances;
import org.cspoker.ai.opponentmodels.weka.instances.ShowdownInstances;
import org.cspoker.common.elements.cards.Card;

import weka.core.Instance;

public class PropositionalDataSetGenerator extends Propositionalizer {

	private static final String nl = InstancesBuilder.nl;
	
	protected static final String hole = "*** HOLE";
	protected static final String flop = "*** FLOP";
	protected static final String turn = "*** TURN";
	protected static final String river = "*** RIVER";
	
	// if you want all files in 'unzipped' then make folder empty string
	protected static final String folder = "FTP_NLH50"; 

	protected final Writer preCheckBetFile;
	protected final Writer postCheckBetFile;	
	protected final Writer preFoldCallRaiseFile;
	protected final Writer postFoldCallRaiseFile;
	protected final Writer showdownFile;
	
	protected final Writer preBetSizeFile;
	protected final Writer postBetSizeFile;
	protected final Writer betSizeFile;
	
	private boolean forgetCurrentGame = false;

	private final HashMap<String, Card> cards = new HashMap<String, Card>();
	private int bb;

	private final PreCheckBetInstances preCheckBetInstance;
	private final PostCheckBetInstances postCheckBetInstance;
	private final PreFoldCallRaiseInstances preFoldCallRaiseInstance;
	private final PostFoldCallRaiseInstances postFoldCallRaiseInstance;
	private final ShowdownInstances showdownInstance;
	
	private final EmptyInstance preBetSizeInstance;
	private final EmptyInstance postBetSizeInstance;
	private final EmptyInstance betSizeInstance;

	public PropositionalDataSetGenerator() throws IOException {
		String tmpFolder = folder + (folder.equals("")?"":"/");
		preCheckBetFile = new OutputStreamWriter(
				new FileOutputStream("output/"+ tmpFolder + "PreCheckBet.arff"));
		postCheckBetFile = new OutputStreamWriter(
				new FileOutputStream("output/"+ tmpFolder + "PostCheckBet.arff"));
		preFoldCallRaiseFile = new OutputStreamWriter(
				new FileOutputStream("output/"+ tmpFolder + "PreFoldCallRaise.arff"));
		postFoldCallRaiseFile = new OutputStreamWriter(
				new FileOutputStream("output/"+ tmpFolder + "PostFoldCallRaise.arff"));
		showdownFile = new OutputStreamWriter(
				new FileOutputStream("output/"+ tmpFolder + "Showdown.arff"));
		
		preBetSizeFile = new OutputStreamWriter(
				new FileOutputStream("output/"+ tmpFolder + "PreBetSize.arff"));
		postBetSizeFile = new OutputStreamWriter(
				new FileOutputStream("output/"+ tmpFolder + "PostBetSize.arff"));
		betSizeFile = new OutputStreamWriter(
				new FileOutputStream("output/"+ tmpFolder + "BetSize.arff"));

		this.preCheckBetInstance = new PreCheckBetInstances("PreCheckBet",
				"@attribute betProb real"+nl+
				"@attribute action {check, bet}"+nl);
		preCheckBetFile.write(preCheckBetInstance.toString());

		this.postCheckBetInstance = new PostCheckBetInstances("PostCheckBet",
				"@attribute betProb real"+nl+
				"@attribute action {check, bet}"+nl);
		postCheckBetFile.write(postCheckBetInstance.toString());

		this.preFoldCallRaiseInstance = new PreFoldCallRaiseInstances("PreFoldCallRaise", 
				"@attribute foldProb real"+nl+
				"@attribute callProb real"+nl+
				"@attribute raiseProb real"+nl+
				"@attribute action {fold,call,raise}"+nl);
		preFoldCallRaiseFile.write(preFoldCallRaiseInstance.toString());

		this.postFoldCallRaiseInstance = new PostFoldCallRaiseInstances("PostFoldCallRaise",  
				"@attribute foldProb real"+nl+
				"@attribute callProb real"+nl+
				"@attribute raiseProb real"+nl+
				"@attribute action {fold,call,raise}"+nl);
		postFoldCallRaiseFile.write(postFoldCallRaiseInstance.toString());

		this.showdownInstance = new ShowdownInstances("Showdown",  
				"@attribute part0Prob real"+nl+
				"@attribute part1Prob real"+nl+
				"@attribute part2Prob real"+nl+
				"@attribute part3Prob real"+nl+
				"@attribute part4Prob real"+nl+
				"@attribute part5Prob real"+nl+
				"@attribute avgPartition {0,1,2,3,4,5}"+nl);
		showdownFile.write(showdownInstance.toString());

		this.preBetSizeInstance = new EmptyInstance("PreBetSize",  
				"@attribute minRaise real"+nl+
				"@attribute maxRaise real"+nl+
				"@attribute relBetSize real"+nl+
				"@attribute blindRelBetSize real"+nl);
		preBetSizeFile.write(preBetSizeInstance.toString());
		
		this.postBetSizeInstance = new EmptyInstance("PostBetSize",  
				"@attribute minRaise real"+nl+
				"@attribute maxRaise real"+nl+ 
				"@attribute relBetSize real"+nl+
				"@attribute blindRelBetSize real"+nl);
		postBetSizeFile.write(postBetSizeInstance.toString());
		
		this.betSizeInstance = new EmptyInstance("BetSize",  
//				"@attribute minRaise real"+nl+
//				"@attribute maxRaise real"+nl+ 
				"@attribute relBetSize real"+nl
//				"@attribute blindRelBetSize real"+nl
				);
		betSizeFile.write(betSizeInstance.toString());
		
		for(Card c:Card.values()){
			cards.put(c.getShortDescription(), c);
		}

	}

	private void close() throws IOException {
		preCheckBetFile.close();
		postCheckBetFile.close();
		preFoldCallRaiseFile.close();
		postFoldCallRaiseFile.close();
		showdownFile.close();
		preBetSizeFile.close();
		postBetSizeFile.close();
		betSizeFile.close();
	}

	private void write(Writer writer, Instance instance) {
		try {
			writer.write(instance.toString()+nl);
			writer.flush();
		} catch (IOException e) {
			e.printStackTrace();
			throw new IllegalStateException(e);
		} catch(ArrayIndexOutOfBoundsException e){
			e.printStackTrace();
			throw new IllegalStateException(e);
		}
	}
	
	protected void logFold(Object actorId) {
//		if(getRound().equals("preflop")){
//			write(preFoldCallRaiseFile, 
//					preFoldCallRaiseInstance.getClassifiedInstance(this, actorId, new Object[]{1,0,0,"fold"}));
//		}else{
//			write(postFoldCallRaiseFile, 
//					postFoldCallRaiseInstance.getClassifiedInstance(this, actorId, new Object[]{1,0,0,"fold"}));
//		}
		
	}


	protected void logCall(Object actorId) {
//		if(getRound().equals("preflop")){
//			write(preFoldCallRaiseFile, 
//					preFoldCallRaiseInstance.getClassifiedInstance(this, actorId, new Object[]{0,1,0,"call"}));
//		}else{
//			write(postFoldCallRaiseFile, 
//					postFoldCallRaiseInstance.getClassifiedInstance(this, actorId, new Object[]{0,1,0,"call"}));
//		}	
	}

	protected void logRaise(Object actorId, double raiseAmount) {
//		if(getRound().equals("preflop")){
//			write(preFoldCallRaiseFile, 
//					preFoldCallRaiseInstance.getClassifiedInstance(this, actorId, new Object[]{0,0,1,"raise"}));
//		}else{
//			write(postFoldCallRaiseFile, 
//					postFoldCallRaiseInstance.getClassifiedInstance(this, actorId, new Object[]{0,0,1,"raise"}));
//		}
		logRaiseAmount(actorId, raiseAmount);
	}

	protected void logCheck(Object actorId) {
//		if(getRound().equals("preflop")){
//			write(preCheckBetFile, 
//					preCheckBetInstance.getClassifiedInstance(this, actorId, new Object[]{0,"check"}));
//		}else{
//			write(postCheckBetFile, 
//					postCheckBetInstance.getClassifiedInstance(this, actorId, new Object[]{0,"check"}));
//		}
	}

	protected void logBet(Object actorId, double raiseAmount) {
//		if(getRound().equals("preflop")){
//			write(preCheckBetFile, 
//					preCheckBetInstance.getClassifiedInstance(this, actorId, new Object[]{1,"bet"}));
//		}else{
//			write(postCheckBetFile, 
//					postCheckBetInstance.getClassifiedInstance(this, actorId, new Object[]{1,"bet"}));
//		}	
		logRaiseAmount(actorId, raiseAmount);
	}

	@Override
	protected void logShowdown(Object actorId, double[] partitionDistr) {
//		Object[] targets = new Object[partitionDistr.length+1];
//		double avgBucket = 0;
//		for(int i=0;i<partitionDistr.length;i++){
//			targets[i]=partitionDistr[i];
//			avgBucket += i*partitionDistr[i];
//		}
//		targets[partitionDistr.length] = (int)Math.round(avgBucket);
//		write(showdownFile, 
//				showdownInstance.getClassifiedInstance(this, actorId, targets));
	}

	private void writeRaise(Object actorId, double minRaise, double maxRaise, 
			double blindRelRaiseAmount, double relRaiseAmount) {
		if (getRound().equals("preflop")) {
			write(preBetSizeFile, preBetSizeInstance.getClassifiedInstance(this, actorId, 
					new Object[]{minRaise, maxRaise, relRaiseAmount, blindRelRaiseAmount}));
		} else {
			write(postBetSizeFile, postBetSizeInstance.getClassifiedInstance(this, actorId, 
					new Object[]{minRaise, maxRaise, relRaiseAmount, blindRelRaiseAmount}));
		}
		write(betSizeFile, betSizeInstance.getClassifiedInstance(this, actorId, 
//					new Object[]{minRaise, maxRaise, relRaiseAmount, blindRelRaiseAmount}));
					new Object[]{relRaiseAmount}));		
	}
	
	private void logRaiseAmount(Object actorId, double raiseAmount){
		PlayerData p = this.getPlayers().get(actorId);
		double bb = p.getBB();
		double relAmount = raiseAmount;
		raiseAmount = raiseAmount * bb;
		double minRaise = (double)getMinRaise(p);
		double maxRaise = (double)getMaxRaise(p);
		raiseAmount = Math.min(raiseAmount, maxRaise);
//		float logBetSize = (float)Math.log(raiseAmount);
		if(Math.abs(minRaise-maxRaise)>0.6) //only when we have a choice
		{
			if(Math.abs(minRaise-raiseAmount)<0.6) {
//				betSizeInstance(p, minRaise, maxRaise, raiseAmount+","+logBetSize+",0,1,0,0,minBet", betSizeClass);
				writeRaise(actorId, minRaise, maxRaise, 0, 0);
			}else if(Math.abs(maxRaise-raiseAmount)<0.6) {
//				betSizeInstance(p, minRaise, maxRaise, raiseAmount+","+logBetSize+",1,0,0,1,allin", betSizeClass);
				writeRaise(actorId, minRaise, maxRaise, Double.NaN, 1);
			}else{
				if(raiseAmount<minRaise || minRaise>maxRaise){
					System.out.println("Skipping illegal bet");
					return;
				}
				double relBetSize = (raiseAmount-minRaise)/(maxRaise-minRaise);
//				betSizeInstance(p, minRaise, maxRaise, raiseAmount+","+logBetSize+","+relBetSize+",0,1,0,avg", betSizeClass);
				writeRaise(actorId, minRaise, maxRaise, relAmount, relBetSize);
//				betSizeInstance(p, minRaise, maxRaise, raiseAmount+","+logBetSize+","+relBetSize+","+(float)Math.log(relBetSize), betSize);
			}
		}
	}

	public void run() throws Exception {
		try {
			int nbFiles = 0;
			final int maxNbFiles = 100;
			String line;			
			File dir1 = new File("../../../data/unzipped");
			String[] children1 = dir1.list();
			if (children1 == null) {
				// Either dir does not exist or is not a directory
			} else {
				for1: for (String element : children1) {
					File child1 = new File(dir1, element);
					String[] children2 = child1.list();
					if (children2 == null) {
						// Either dir does not exist or is not a directory
					} else {
						for (String element2 : children2) {
							if (element.equals(folder) || folder.equals("")) {
								// Get filename of file or directory
								System.out.println("Starting file #"+(nbFiles+1)+"/"+maxNbFiles+": " + element
										+ "/" + element2);
								BufferedReader r = new BufferedReader(
										new FileReader(new File(child1, element2)));
								while ((line = r.readLine()) != null) {
									try {
										doLine(line);
									} catch (Exception e) {
										System.out.println(line);
										throw e;
									}
								}
								r.close();
								nbFiles ++;
								if(nbFiles>=maxNbFiles) break for1;
							}
						}
					}
				}
			}
		} finally {
			close();
		}
	}

	private void doLine(String line) throws IOException {
		// inputRaise.write(line+"\n");
		// foldFile.write(line+"\n");
		//		System.out.println(line);
		if (line.startsWith("Full Tilt Poker Game ")) {
			//			if(line.startsWith("Full Tilt Poker Game #7148395139")){
			//				System.out.println("Found needle");
			//			}
			int temp = line.indexOf("/");
			this.bb = parseAmount(line.substring(temp + 2,
					line.indexOf(" ", temp + 2)));
			forgetCurrentGame = false;
			signalNewGame();
			signalBBAmount(bb);
		} else if (!forgetCurrentGame) {
			if (line.startsWith("Seat ")) {
				if (line.endsWith("(0)")) {
					forgetCurrentGame = true;
				} else {
					int startName = line.indexOf(":") + 2;
					int startDollar = line.indexOf("(", startName);
					int stack = parseAmount(line.substring(startDollar + 2,
							line.indexOf(")", startDollar)));
					String name = line.substring(startName, startDollar - 1);
					signalSeatedPlayer(stack, name);
				}
			} else if (line.startsWith("*** ")) {
				if (line.startsWith("*** SUMMARY ***")) {
					forgetCurrentGame = true;
					signalShowdown();
				} else {
					if(line.startsWith(hole)){
						signalCommunityCards(EnumSet.noneOf(Card.class));
					}
					if (line.startsWith(flop)) {
						signalFlop();
						String[] cardsString = line.substring(line.indexOf("[")).replaceAll("\\[", "").replaceAll("\\]", "").split(" ");
						EnumSet<Card> cardsSet = EnumSet.of(cards.get(cardsString[0]),cards.get(cardsString[1]),cards.get(cardsString[2]));
						signalCommunityCards(cardsSet);
					} else if (line.startsWith(turn)) {
						signalTurn();
						String[] cardsString = line.substring(line.indexOf("[")).replaceAll("\\[", "").replaceAll("\\]", "").split(" ");
						EnumSet<Card> cardsSet = EnumSet.of(cards.get(cardsString[0]),cards.get(cardsString[1]),cards.get(cardsString[2]),cards.get(cardsString[3]));
						signalCommunityCards(cardsSet);
					} else if (line.startsWith(river)) {
						signalRiver();
						String[] cardsString = line.substring(line.indexOf("[")).replaceAll("\\[", "").replaceAll("\\]", "").split(" ");
						EnumSet<Card> cardsSet = EnumSet.of(cards.get(cardsString[0]),cards.get(cardsString[1]),cards.get(cardsString[2]),cards.get(cardsString[3]),cards.get(cardsString[4]));
						signalCommunityCards(cardsSet);
					}
				}
			} else if (line.contains(":")) {
				// ignore chat message
			} else {
				boolean isAllIn = line.contains("all in");
				if (line.contains(" posts the small blind")) {
					String id = line.substring(0, line
							.indexOf(" posts the small blind"));

					signalBlind(isAllIn, id, bb/2);
				} else if (line.contains(" posts the big blind")) {
					String id = line.substring(0, line
							.indexOf(" posts the big blind"));
					signalBlind(isAllIn, id, bb);
				} else if (line.endsWith(" folds")) {
					String id = line
					.substring(0, line.indexOf(" folds"));
					signalFold(id);
				} else if (line.contains(" calls")) {
					int allinIndex = line.lastIndexOf(", and is all in");
					if (allinIndex <= 0) {
						allinIndex = line.length();
					}
					String id = line
					.substring(0, line.indexOf(" calls"));
					int movedAmount = parseAmount(line.substring(
							line.indexOf("$") + 1, allinIndex));
					signalCall(isAllIn, id, movedAmount);
				} else if (line.contains(" raises to")) {
					int allinIndex = line.lastIndexOf(", and is all in");
					if (allinIndex <= 0) {
						allinIndex = line.length();
					}
					int maxBetParsed = parseAmount(line.substring(
							line.indexOf("$") + 1, allinIndex));
					String id = line.substring(0, line
							.indexOf(" raises to"));
					signalRaise(isAllIn, id, maxBetParsed);
				} else if (line.endsWith(" checks")) {
					String id = line.substring(0, line
							.indexOf(" checks"));
					signalCheck(id);
				} else if (line.contains(" bets ") && !line.contains("bo bets all")) {
					String id = line.substring(0, line.indexOf(" bets"));
					int allinIndex = line.lastIndexOf(", and is all in");
					if (allinIndex <= 0) {
						allinIndex = line.length();
					}
					int maxBetParsed = parseAmount(line.substring(line.indexOf("$") + 1,
							allinIndex));
					//cannot be bet by big blind, is raise in dataset
					signalBet(isAllIn, id, maxBetParsed);
				} else if (line.contains(" shows [")){
					int showsIndex = line
					.indexOf(" shows [");
					String id = line.substring(0, showsIndex);

					int start = showsIndex+8;
					String[] cardStrings = line.substring(start, line.indexOf("]",start)).split(" ");
					signalCardShowdown(id,cards.get(cardStrings[0]),cards.get(cardStrings[1]));
				}
			}
		}
	}

	private int parseAmount(String stringAmount) {
		return (int)Math.round(100*Double.parseDouble(stringAmount.replaceAll(",", "")));
	}
}
